//! # Event Queue
//!
//! This crate provides a thread-safe event queue system for collecting and managing various types
//! of events that occur during GraphQL gateway operation execution.
//!
//! The event queue stores events only if the gateway is started with a hooks extension, which defines
//! an event filter in its configuration. Otherwise every event push is a no-op.

mod events;
mod filter;

pub use events::*;
pub use filter::*;

use crossbeam_queue::SegQueue;

/// A thread-safe queue for collecting events during gateway operation execution.
///
/// The `EventQueue` provides a centralized mechanism for collecting various types of events
/// that occur during request processing. It uses a lock-free queue internally for efficient
/// concurrent access.
///
/// Events are only collected if an extension is configured and interested in receiving them,
/// as determined by the event filter configuration.
#[derive(Debug)]
pub struct EventQueue {
    queue: SegQueue<Event>,
    filter: Option<EventFilter>,
}

impl Default for EventQueue {
    fn default() -> Self {
        Self {
            queue: SegQueue::new(),
            filter: None,
        }
    }
}

impl EventQueue {
    /// Creates a new `EventQueue` instance.
    ///
    /// # Arguments
    ///
    /// * `hooks` - Optional extension configuration that determines whether events should be
    ///   collected and which types of events to filter.
    ///
    /// # Returns
    ///
    /// A new `EventQueue` instance. If no extension is provided, the queue will be inactive
    /// and won't collect any events.
    pub fn new(filter: Option<EventFilter>) -> Self {
        let queue = SegQueue::new();

        Self { queue, filter }
    }

    /// Pushes a GraphQL operation execution event to the queue.
    ///
    /// This method queues an event representing the execution of a GraphQL operation,
    /// including details like the operation name, document, execution duration, and response status.
    ///
    /// The event is only queued if operation events are allowed by the current filter configuration.
    /// The builder is only consumed and built if the event will actually be pushed, avoiding
    /// expensive operations when events are filtered out.
    ///
    /// # Arguments
    ///
    /// * `builder` - The operation execution event builder
    pub fn push_operation(&self, builder: ExecutedOperationBuilder<'_>) {
        if !self.must_keep_event(EventFilterType::Operation) {
            return;
        }

        self.queue.push(Event::Operation(builder.build()));
    }

    /// Pushes a subgraph request event to the queue.
    ///
    /// This method queues an event representing a request made to a federated subgraph,
    /// including details about the request method, URL, response status, timing, and any retries.
    ///
    /// The event is only queued if subgraph request events are allowed by the current filter configuration.
    /// The builder is only consumed and built if the event will actually be pushed, avoiding
    /// expensive operations when events are filtered out.
    ///
    /// # Arguments
    ///
    /// * `builder` - The subgraph request event builder
    pub fn push_subgraph_request(&self, builder: ExecutedSubgraphRequestBuilder<'_>) {
        if !self.must_keep_event(EventFilterType::SubgraphRequest) {
            return;
        }

        self.queue.push(Event::Subgraph(builder.build()));
    }

    /// Pushes an HTTP request event to the queue.
    ///
    /// This method queues an event representing a generic HTTP request made during
    /// operation execution (not including subgraph requests, which have their own event type).
    ///
    /// The event is only queued if HTTP request events are allowed by the current filter configuration.
    /// The builder is only consumed and built if the event will actually be pushed, avoiding
    /// expensive operations when events are filtered out.
    ///
    /// # Arguments
    ///
    /// * `builder` - The HTTP request event builder
    pub fn push_http_request(&self, builder: ExecutedHttpRequestBuilder<'_>) {
        if !self.must_keep_event(EventFilterType::HttpRequest) {
            return;
        }

        self.queue.push(Event::Http(builder.build()));
    }

    /// Pushes a custom extension event to the queue.
    ///
    /// This method queues a custom event generated by an extension. Extensions can use this
    /// to emit their own events with arbitrary data payloads.
    ///
    /// The event is only queued if extension events are allowed by the current filter configuration.
    pub fn push_extension_event<E>(&self, f: impl FnOnce() -> Result<ExtensionEvent, E>) -> Result<(), E> {
        if !self.must_keep_event(EventFilterType::Extension) {
            return Ok(());
        }

        self.queue.push(Event::Extension(f()?));
        Ok(())
    }

    /// Pops an event from the queue.
    ///
    /// This method removes and returns the next event from the queue if one is available.
    /// If the queue is empty or not initialized (no extension configured), it returns `None`.
    ///
    /// # Returns
    ///
    /// `Some(Event)` if an event was available, `None` if the queue is empty or inactive
    pub fn pop(&self) -> Option<Event> {
        self.queue.pop()
    }

    /// Checks whether a given event type is allowed by the current filter configuration.
    fn must_keep_event(&self, event_type: EventFilterType) -> bool {
        match &self.filter {
            Some(EventFilter::All) => true,
            Some(EventFilter::Types(types)) => types.contains(event_type),
            None => false,
        }
    }
}
